import re




"""
use:

Call this from inside the directory with a file called "PROTOCOL".
It will generate your C# files from "PROTOCOL" (which has the format below).

It works based on the current working directory, not where it actually is.
"""

"""
PROTOCOL FORMAT:
<ELEMENT>\n
{<ATTRIBUTE>\n}*
----\n

<ELEMENT> ::= <!ELEMENT $name> |
                <!ELEMENT $name ($child)>
                <!ELEMENT $name ($child | $other)>
                <!ELEMENT $name ($child, $other)>
                <!ELEMENT $name (($child, $other) | ($this, $that))>
                <!ELEMENT $name ($child, ($other)*[NameForOther])>
<ATTRIBUTE> ::= <!ATTLIST $elementname $attributename $attribute type #REQUIRED>

NOTES:  This only supports required attributes.
        You may deviate from the types of attributes and use
        valid c# types like "int."

"""

INCLUDES = """
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using OWED.Network.Messages.General;
using OWED.Network.Messages;
using OWED.Network.Messages.Reply;
"""
HEADERMSG = """
/* GENERATED CODE:
 *  Any modifications you make are guaranteed
 *  to be deleted when this is regenerated.
 * Please edit "PROTOCOLS" inside this directory
 *
 * Names are all upper case because of "String" and "string."
 */
"""

LOCATION = "OWED.Network"

TYPETRANSFORMS = {"CDATA":"String",
                  "ID":"int",
                  "ENTITIES":"String",
                  "BOOLEAN":"bool",
                  "NUMBER":"int"}

"""
These define how to print various types of
xml items
"""
class ItemBuilder:
    def NewElementBuilder(self, elemtext):
        if elemtext == "#PCDATA":
            return PCDATABuilder()
        elif elemtext.find("*") >= 0:
            item, name = elemtext.split("*")
            return ElementCollectionBuilder(item, name[1:-1])
        else:
            return ElementBuilder(elemtext)

    NewElementBuilder = classmethod(NewElementBuilder)
    
    def write_instance_variable(self, depth, fp):
        raise Exception("ABSTRACT CLASS!")
    def write_constructor_argument(self, fp):
        """
        fp is a file pointer/stream
        """
        raise Exception("ABSTRACT CLASS!")
    def write_constructor_assignment(self, depth, fp):
        raise Exception("ABSTRACT CLASS!")

    def write_accessor_method(self, depth, fp):
        raise Exception("ABSTRACT CLASS!")

    def write_fromxml_assignment(self, depth, fp, path):
        """
        Writes what it needs to do to get info from the xml.
        path is the xml path to where the current node is.  Like:
            "this/that" and under "that" would be the current element/attribute
        """
        raise Exception("ABSTRACT CLASS!")


    def write_toxml_assignment(self, depth, fp, docname):
        """
        Writes what it needs to put info into the xml
        docname is the C# variable name of the document to insert into
        Returns a string representing its C# variable name
        """
        raise Exception("ABSTRACT CLASS!")
        

"""
Defines a general Xml Element
"""
class ElementBuilder(ItemBuilder):
    def __init__(self, elemtext):
        if elemtext == "#PCDATA":
            self.ElemText = "String"
            self.PCDATA = True
        else:
            self.ElemText = elemtext
            self.PCDATA = False
        self.Name = elemtext.lower()

    def write_instance_variable(self, depth, fp):
        fp.write("\t"*depth)
        fp.write(self.ElemText)
        fp.write(" my_")
        fp.write(self.Name)
        fp.write(";")
        
        
    def write_constructor_argument(self, fp):
        fp.write(self.ElemText)
        fp.write(" ")
        fp.write(self.Name)

    def write_accessor_method(self, depth, fp):
        fp.write("\t"*depth)
        fp.write("public %s %s { get { return my_%s; } }" % (
            self.ElemText, self.ElemText, self.Name))
        
    def write_constructor_assignment(self, depth, fp):
        fp.write("\t"*depth + "my_")
        fp.write(self.Name)
        fp.write(" = ")
        fp.write(self.Name)
        fp.write(";")
        
    def write_fromxml_assignment(self, depth, fp, path):
        fp.write("\t"*depth)
        fp.write("doc = new XmlDocument();\n")
        fp.write("\t"*depth)
        fp.write("doc.AppendChild(doc.ImportNode(xml.SelectSingleNode(")
        fp.write("\"")
        fp.write(path + "/" + self.ElemText)
        fp.write("\"), true));\n")
        fp.write("\t"*depth)
        fp.write(self.ElemText)
        fp.write(" ")
        fp.write(self.Name)
        fp.write(" = ")
        fp.write(self.ElemText)
        fp.write(".FromXml(doc);")     

        return self.Name

    def write_toxml_assignment(self, depth, fp, docname):
        fp.write("\t"*depth + "if (my_")
        fp.write(self.Name)
        fp.write(" != null) {\n")
        fp.write("\t"*(depth+1) + "XmlNode node;\n")
        fp.write("\t"*(depth+1) + "node = " + docname + ".ImportNode(my_")
        fp.write(self.Name)
        fp.write(".ToXml().FirstChild, true);\n")
        fp.write("\t"*(depth+1) + docname + ".FirstChild.AppendChild(node);\n")
        fp.write("\t"*depth + "}\n")        

    def Element(self):
        return self.ElemText


class ElementCollectionBuilder(ElementBuilder):
    def __init__(self, oftype, named):
        print oftype, named
        self.PCDATA = False
        self.Name = named
        self.ElemText = "List <" + oftype + ">"
        self.Type = oftype

    def write_instance_variable(self, depth, fp):
        ElementBuilder.write_instance_variable(self, depth, fp)


    def write_accessor_method(self, depth, fp):
        fp.write("\t"*depth)
        fp.write("public %s %s { get { return my_%s; } }" % (
            self.ElemText, self.Name, self.Name))


    def write_fromxml_assignment(self, depth, fp, path):
        fp.write("\t"*depth)
        lstname = "list_" + self.Name
        fp.write("XmlNodeList %s = xml.SelectNodes(\"" % lstname)
        fp.write(path)
        fp.write("/" + self.Type + "\");\n")
        fp.write("\t"*depth)
        fp.write("List <" + self.Type + "> " + self.Name)
        fp.write(" = new List <" + self.Type)
        fp.write(">();\n")
        fp.write("\t"*depth)
        fp.write("foreach (XmlNode node in %s) {\n" % lstname)
        fp.write("\t"*(depth+1) + "doc = new XmlDocument();\n")
        fp.write("\t"*(depth+1))
        fp.write("doc.AppendChild(doc.ImportNode(node, true));\n")
        fp.write("\t"*(depth+1) + self.Name)
        fp.write(".Add(" + self.Type + ".FromXml(doc));\n")
        fp.write("\t"*depth + "}")
        

        return self.Name


    def Element(self):
        return self.Type


    def write_toxml_assignment(self, depth, fp, docname):
        fp.write("\t"*depth + "if (my_")
        fp.write(self.Name)
        fp.write(" != null) {\n")

        fp.write("\t"*(depth+1) + "XmlNode node;\n")
        fp.write("\t"*(depth+1) + "foreach (")
        fp.write(self.Type)
        fp.write(" item in my_")
        fp.write(self.Name)
        fp.write(") {\n")
        fp.write("\t"*(depth+2))
        fp.write("node = doc.ImportNode(item.ToXml().FirstChild, true);\n")
        fp.write("\t"*(depth+2))
        fp.write("doc.FirstChild.AppendChild(node);\n")
        fp.write("\t"*(depth+1) + "}\n")

        fp.write("\t"*depth + "}\n")        


"""
This handles PCDATA elements.
They're slightly special from regular Elements, but not by much.
"""
class PCDATABuilder(ElementBuilder):
    def __init__(self):
        self.ElemText = "String"
        self.Name = "value"
        
    def write_fromxml_assignment(self, depth, fp, path):
        fp.write("\t"*depth)
        fp.write(self.ElemText)
        fp.write(" ")
        fp.write(self.Name)
        fp.write(" = ")
        fp.write("xml.SelectSingleNode(")
        fp.write("\"")
        fp.write(path)
        fp.write("\").FirstChild.Value;")

        return self.Name

    def write_toxml_assignment(self, depth, fp, docname):
        fp.write("\t"*depth + "if (my_")
        fp.write(self.Name)
        fp.write(" != null) {\n")

        fp.write("\t"*(depth+1))
        fp.write(docname + ".FirstChild.AppendChild(")
        fp.write(docname + ".CreateTextNode(my_")
        fp.write(self.Name)
        fp.write("));\n")

        fp.write("\t"*depth + "}\n")        

    def write_accessor_method(self, depth, fp):
        fp.write("\t"*depth)
        fp.write("public %s %s { get { return my_%s; } }" % (
            self.ElemText, self.Name[0].upper() + self.Name[1:], self.Name))
        



"""
This defines an Attribute.
"""
class AttributeBuilder(ItemBuilder):
    def __init__(self, name, type_):
        if TYPETRANSFORMS.has_key(type_):
            self.Type = TYPETRANSFORMS[type_]
        else:
            self.Type = type_
        self.Name = name

    def write_instance_variable(self, depth, fp):
        fp.write("\t"*depth)
        fp.write(self.Type)
        fp.write(" my_")
        fp.write(self.Name.lower())
        fp.write(";")

    def write_accessor_method(self, depth, fp):
        fp.write("\t"*depth)
        fp.write("public %s %s { get { return my_%s; } }" % (
            self.Type, self.Name, self.Name.lower()))


    def write_constructor_argument(self, fp):
        """
        fp is a file pointer/stream
        """
        fp.write(self.Type)
        fp.write(" ")
        fp.write(self.Name.lower())
    def write_constructor_assignment(self, depth, fp):
        fp.write("\t"*depth + "my_")
        fp.write(self.Name.lower())
        fp.write(" = ")
        fp.write(self.Name.lower())
        fp.write(";")
        
    def write_fromxml_assignment(self, depth, fp, path):
        fp.write("\t"*depth + self.Type + " ")
        fp.write(self.Name.lower())
        if (self.Type == "String"):
            fp.write(" = xml.SelectSingleNode(\"")
        else:
            fp.write(" = ")
            fp.write(self.Type)
            fp.write(".Parse(xml.SelectSingleNode(\"")
        fp.write(path)
        fp.write("\").Attributes.GetNamedItem(\"")
        fp.write(self.Name)
        if self.Type == "String":
            fp.write("\").Value;")
        else:
            fp.write("\").Value);")
        
        return self.Name.lower()


    def write_toxml_assignment(self, depth, fp, docname):
        fp.write("\t"*depth + "attr = " + docname)
        fp.write(".CreateAttribute(\"")
        fp.write(self.Name)
        fp.write("\");\n")
        fp.write("\t"*depth + "attr.Value = ")
        fp.write("my_" + self.Name)
        if self.Type != "String":
            fp.write(".ToString()")
        fp.write(";\n")
        fp.write("\t"*depth + docname)
        fp.write(".FirstChild.Attributes.Append(attr);\n")
        
        


class ClassBuilder:
    """
    This class is for building a C# file from a chunk of the protocol.
    """
    def __init__(self, element, attributes=[]):
        self.Element = element
        self.Attributes = attributes

        element = element.strip()
        element = element[element.find(" "):]
        element = element[element.find("("):]
        element = element[:element.rfind(">")]

        self.Elements = element

        try:
            self.Name = re.compile("<!ELEMENT ([A-z]+)"
                                           ).findall(self.Element)[0]
        except (AttributeError):
            print "BAD FORMATTING!"
        print "Element:", self.Name

        parsed_attributes = []
        attr_types = []
        for attr in attributes:
            if len(attr.strip()) > 0:
                parsed_attributes.append(attr.split(" ")[2])
                attr_types.append(attr.split(" ")[3])



        if self.Element.find("(") < 0:
            self.__fill_kids__("", parsed_attributes, attr_types)
        else:
            self.__fill_kids__(self.Elements,
                           parsed_attributes, attr_types)
        


        self.FileName = self.Name + ".cs"

    def GetFileName(self):
        return self.FileName

    #this function grabs the children from the
    # list of possible child elements and
    # from the list of attributes.
    def __fill_kids__(self, element, attributes, attr_types):
        self.ConstructorAttr = []
        for i in range(0, len(attributes)):
            self.ConstructorAttr.append(AttributeBuilder(attributes[i],
                                                         attr_types[i]))
                                                        
            
        self.Constructors = []        
        #parse child nodes:
        options = element.split("|")
        for option in options:
            if option.strip() == "":
                self.Constructors.append([])
                continue
            option = option.strip()
            if (option[0] == '(' and
                option[-1] == ')'):
                option = option[1:-1]
            elif option[0] == '(':
                option = option[1:]
            
            items = option.split(",")

            fixed = []
            for item in items:
                item = item.replace(")", "").replace("(", "")
                fixed.append(ItemBuilder.NewElementBuilder(item.strip()))

            self.Constructors.append(fixed)

    def __write_constructor__(self, fp, constructor):
        fp.write("\t\tpublic " + self.Name + " (")
        for i in range(0, len(self.ConstructorAttr)):
            self.ConstructorAttr[i].write_constructor_argument(fp)
            if (i+1 < len(self.ConstructorAttr)):
                fp.write(", ")
            elif len(constructor) > 0:
                fp.write(", ")

        for i in range(0, len(constructor)):
            item = constructor[i]
            item.write_constructor_argument(fp)
            
            if (i+1 < len(constructor)):
                fp.write(", ")
        fp.write(")\n")
        
        #the constructor body:
        fp.write("\t\t{\n")
        for item in self.ConstructorAttr+constructor:
            item.write_constructor_assignment(3, fp)
            fp.write("\n")
            
        fp.write("\t\t}\n")

        
    def __write_header__(self, fp):
        fp.write(HEADERMSG)
        fp.write(INCLUDES)

    def __write_fromxml__(self, fp):
        fp.write("\t\tpublic static new ")
        fp.write(self.Name)
        fp.write(" FromXml(XmlDocument xml)\n")
        fp.write("\t\t{\n")

        #write the attributes out:
        parameters = []
        for i in range(0, len(self.ConstructorAttr)):
            item = self.ConstructorAttr[i]
            item = item.write_fromxml_assignment(3, fp, self.Name)
            fp.write("\n")
            parameters.append(item)

        #wondering what to do about conditionals...
        #need to get the sub items
        constructors = []
        for item in self.Constructors:
            constructors.append(item)

        def comp(l1, l2):
            return cmp(len(l1), len(l2))
        constructors.sort(comp, reverse=True)

        allpaths = False
        kids = False
        for constr in constructors:
            for c in constr:
                if not isinstance(item, PCDATABuilder):
                    kids = True
        
        if kids:
            fp.write("\t\t\tXmlDocument doc;\n")
        for constr in constructors:
            curparams = []
            for i in parameters:
                curparams.append(i)
            printed = 0
            if len(constr) > 0:
                fp.write("\t\t\tif (")
                for i in range(0, len(constr)):
                    item = constr[i]
                    if isinstance(item, PCDATABuilder):
                        printed += 1
                        fp.write("xml.SelectSingleNode(\"")
                        fp.write(self.Name + "\")")
                        fp.write(".FirstChild.Value != null")
                    elif not isinstance(item, ElementCollectionBuilder):
                        printed += 1
                        fp.write("xml.SelectSingleNode(\"")
                        fp.write(self.Name + "/")
                        fp.write(item.Element())
                        fp.write("\") != null")
                    if i+1 < len(constr):
                        fp.write(" &&\n\t\t\t\t\t")
                if printed == 0:
                    fp.write("true")
                fp.write(") {\n")
                for item in constr:
                    item = item.write_fromxml_assignment(4, fp, self.Name)
                    fp.write("\n\n")
                    curparams.append(item)
            
            
            if (len(constr) > 0):
                fp.write("\t")
            fp.write("\t\t\treturn new ")
            fp.write(self.Name)
            fp.write("(")
            for i in range(0, len(curparams)):
                param = curparams[i]
                fp.write(param)
                if i+1 < len(curparams):
                    fp.write(", ")

            fp.write(");\n")
            if (len(constr) > 0 and printed > 0):
                fp.write("\t\t\t}\n")
            elif len(constr) > 0:
                fp.write("\t\t\t}\n")
                allpaths = True
            else:
                allpaths = True

        if not allpaths:
            fp.write("\t\t\tthrow new ConnectionException(\"")
            fp.write("Bad document supposedly of type:")
            fp.write(self.Name)
            fp.write("\");\n")
        fp.write("\t\t}\n")

    def __write_toxml__(self, fp):
        fp.write("\t\tpublic override XmlDocument ToXml()")
        fp.write("\t\t{\n")
        fp.write("\t\t\tXmlDocument doc = new XmlDocument();\n\n")

        fp.write("\t\t\tdoc.AppendChild(doc.CreateElement(\"")
        fp.write(self.Name)
        fp.write("\"));\n")
        
        #attributes:
        if len(self.ConstructorAttr) > 0:
            fp.write("\t\t\tXmlAttribute attr;\n")
            for item in self.ConstructorAttr:
                item.write_toxml_assignment(3, fp, "doc")
                fp.write("\n")

        fp.write("\n")
        subelements = {}
        for item in self.Constructors:
            for i in item:
                subelements[i] = True
        #sub elements:
        if len(subelements) > 0:
            for item in subelements:
                item.write_toxml_assignment(3, fp, "doc")
                fp.write("\n")

        fp.write("\t\t\treturn doc;\n")
        fp.write("\t\t}\n")


    def Write(self, fp):
        self.__write_header__(fp)        
        fp.write("\n\nnamespace " + LOCATION + "\n")
        fp.write("{\n")
        fp.write("\tpublic class " + self.Name + ": IProtocolItem \n")
        fp.write("\t{\n")

        #writing instance variables:
        done = {}
        for i in self.Constructors:
            for item in i:
                if not done.has_key(item.Element()):
                    item.write_instance_variable(2, fp)
                    done[item.Element()] = True
                    fp.write("\n")
                    
        for item in self.ConstructorAttr:
            item.write_instance_variable(2, fp)
            fp.write("\n")

        #writing constructors:
        for constr in self.Constructors:
            self.__write_constructor__(fp, constr)

        #writing accessors:
        done = {}
        for i in self.Constructors:
            for item in i:
                if not done.has_key(item.Element()):
                    item.write_accessor_method(2, fp)
                    done[item.Element()] = True
                    fp.write("\n")
        for item in self.ConstructorAttr:
            item.write_accessor_method(2, fp)
            fp.write("\n")

        fp.write("\n\n")
        self.__write_fromxml__(fp)
        fp.write("\n\n")
        #write ToXml():
        self.__write_toxml__(fp)        

        fp.write("\t}\n")
        fp.write("}\n")

        fp.close()

def GenerateChunk(chunk):
    lines = chunk.split("\n")
    element = lines[0]
    if len(lines) > 1:
        attributes = lines[1:]
    else:
        attributes = []


    factory = ClassBuilder(element, attributes)
    fp = open(factory.GetFileName(), "w")
    factory.Write(fp)


def Generate(fileName):
    fp = open(fileName, "r")
    data = fp.read()
    fp.close()

    chunks = data.split("----\n")

    for chunk in chunks:
        GenerateChunk(chunk)

    

import os
import os.path

if __name__ == "__main__":
    cur = os.getcwd()
    paths = cur.split(os.path.sep)
    LOCATION += "." +  ".".join(paths[paths.index("Messages"):])
    """
    LOCATION is now something like "OWED.Network.Messages.General" based
    on the cwd.
    """

    print os.getcwd() + os.path.sep + "PROTOCOL"
    Generate(os.getcwd() + os.path.sep + "PROTOCOL")
    raw_input()
    
